//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

//
//
//
//	The ShapeMonitor is a singleton class that watches shape or texture nodes,
//	and keep track of which one changed since the last export. It is used to keep
//	the IFX scenegraph up-to-date in respect to textures.
//
//	Client code can:
//
//		- Ask for a pointer to the ShapeMonitor. (using the instance() function)
//		  If it doesn't already exist, it is created.
//		- Ask the ShapeMonitor to watch a specific maya node name (specifying whether it's
//		  a texture, or a shape) and give a unique texture name. Doing so creates some callbacks
//		  on the specified node, so that we know when it changes. When a specific node changes,
//		  it's unique name is appended to the list (actually, set) of dirty textures.
//		- Ask for the list of dirty textures. Those should be removed from the IFX scenegraph,
//		  and will possibly be regenerated, if they still exist.
//		- Clear the list of dirty textures.
//
//	Additionally, once the ShapeMonitor is no longer necessary (eg: the scene is being closed), 
//  it can be destroyed using the destroy() function. Finally, all callbacks and data structures
//  can be cleared by calling the initialize() function.

#include <assert.h>

#include <maya/MSelectionList.h>
#include <maya/MConditionMessage.h>
#include <maya/MFnDagNode.h>
#include <maya/MDagPath.h>

#include "ShapeMonitor.h"



//
// PUBLIC INTERFACE
// ----------------
//


/* static */ 
ShapeMonitor* ShapeMonitor::instance()
{
	// If there is no texture monitor in use, create one.
	if (privateInstance == NULL)
	{
		privateInstance = new ShapeMonitor();
	}

	return privateInstance;
}

/* static */
ShapeMonitor* ShapeMonitor::initialize()
{
	destroy();
	return instance();
}

/* static */
void ShapeMonitor::destroy()
{
	if (privateInstance != NULL)
		delete privateInstance;
}

void ShapeMonitor::watch(MString mayaNodeName, MonitoredObject* pMon)
//
// Description:
//
//    Start watching the specified mayaNodeName for changes. (If the node is part of the DAG, that
// should be a fully-specified DAG name). This function will store all available information
// inside a MonitorObject for future reference.
//
// Arguments:
//		mayaNodeName: DG (or fully-qualified DAG) name.
//		uniqueTextureName: The corresponding texture name. This texture should correspond
//						   directly to an IFX texture resource.
{
	// Check if, per chance, this monitored object already exists.
	// If it does, no need to create a new one.
	if (retrieveMonitorObject(mayaNodeName))
		return;

	MStatus stat;

	//
	// Get the node.
	//
	MObject node;
	MSelectionList selList;
	selList.add(mayaNodeName);
	selList.getDependNode(0, node);

	//
	//	Attach the callbacks (dirty or renamed node)
	//
	pMon->mayaNodeName = mayaNodeName;

	pMon->dirtyCallbackId = MNodeMessage::addNodeDirtyCallback(node, watchedObjectDirtyCallback, pMon, &stat ); 
	assert(stat);

	pMon->renamedCallbackId = MNodeMessage::addNameChangedCallback ( node, watchedObjectRenamedCallback, pMon, &stat); 
	assert(stat);

	// Store the pertinent information in the Monitored Objects Array.
	monitoredObjectsPtrArray.append(pMon);
}



// Stop watching node(s) that share the given uniqueTextureName.
// In addition, the uniqueTextureName is automatically added to the list of dirty textures.
// (If more than one node has the given uniqueTextureName, we stop to watch all of those that match. 
// This is done to minimize callback overhead.)
void ShapeMonitor::stopWatching(MString mayaNodeName)
{
	for (int i = monitoredObjectsPtrArray.length()-1; i >= 0; i--)
	{
		// If this record's node name matches...
		if (monitoredObjectsPtrArray[i]->mayaNodeName == mayaNodeName)
		{
			// Remove the callbacks for this node.
			removeCallbacks(monitoredObjectsPtrArray[i]);

			// Remove this element from the monitored objects array.
			delete monitoredObjectsPtrArray[i];
			monitoredObjectsPtrArray.removeElements(i, i, false);
		}
	}
}

// Stop watching all nodes. This detaches the callbacks.
void ShapeMonitor::stopWatchingAll()
{
	for (int i = monitoredObjectsPtrArray.length()-1; i >= 0; i--)
	{
		// Remove the callbacks for this node.
		removeCallbacks(monitoredObjectsPtrArray[i]);

		// Remove this element from the monitored objects array.
		delete monitoredObjectsPtrArray[i];
		monitoredObjectsPtrArray.removeElements(i, i, false);
	}
}

// Remove any DAG object not in the selectedObjects list.
void ShapeMonitor::stopWatchingUnselectedDagObjects(MSelectionList& selectedObjects)
{
	// For each monitored object...
	for (int i = monitoredObjectsPtrArray.length()-1; i >= 0; i--)
	{
		MonitoredObject *pMonObject = monitoredObjectsPtrArray[i];

		MStatus stat;

		// Get an MObject for the MonitoredObject->mayaNodeName.
		MDagPath dagpath;
		MSelectionList selList;
		selList.add(pMonObject->mayaNodeName);
		stat = selList.getDagPath(0, dagpath);

		// If the MObject is a DAG node...
		if (stat)
		{
			bool found = false;

			// Check if the dag path is included in the selectedObjects list.
			// For example, say that dagpath = "|group1|group2|pSphere|pSphereShape",
			// selectedObjects contains "|group1|group2".
			// We first check if dagpath is included in selectedObjects. If that's not the
			// case, we pop() one component, so that dagpath = "|group1|group2|pSphere", then
			// check again. We do that until either the dagpath is found to be included in
			// the selectedObjects list, or until there's no component left in dagpath.
			while (!found && dagpath.length() > 0)
			{
				// Since we store the shape name (as opposed to the parent transform dagpath),
				// we need to pop() to get the parent transform dagpath.
				dagpath.pop();
				
				MObject component;

				// Check if the dag path is included in the objects list.
				if (selectedObjects.hasItemPartly(dagpath, component))
					found = true;
			}

			// If the object was not in the selectedObjects list, stop watching it.
			if (!found)
				stopWatching(pMonObject->mayaNodeName);
		}
	}
}



//
// PRIVATE IMPLEMENTATION
// ----------------------
//


// The private instance points to the only texture monitor in memory, 
// or NULL if there is no instance.
ShapeMonitor* ShapeMonitor::privateInstance = NULL;


// Both the constructor and destructor assume that "this" is the only instance
// of ShapeMonitor in memory. We can ensure this condition since both
// functions are private.
ShapeMonitor::ShapeMonitor()
{
}

ShapeMonitor::~ShapeMonitor()
{
	stopWatchingAll();
}

MonitoredObject* ShapeMonitor::retrieveMonitorObject(MString mayaNodeName)
{
	for (int i = 0; i < (int) monitoredObjectsPtrArray.length(); i++)
	{
		// If this element's node name and filename matches...
		if (monitoredObjectsPtrArray[i]->mayaNodeName == mayaNodeName)
			return monitoredObjectsPtrArray[i];
	}

	return NULL;
}

// Attempt to detach all of the callbacks for a specific monitoredObject.
// Assert if any of them fails.
void ShapeMonitor::removeCallbacks(MonitoredObject *mon)
{
	MStatus stat;

	stat = MMessage::removeCallback(mon->dirtyCallbackId);
	assert(stat);

	stat = MMessage::removeCallback(mon->renamedCallbackId);
	assert(stat);
}

void ShapeMonitor::watchedObjectDirtyCallback( void* clientData )
{
	MonitoredObject *mon = (MonitoredObject*) clientData;

	privateInstance->stopWatching(mon->mayaNodeName);
	// Note: after this call the monitored object has been deleted, 
	// so don't do anything with this pointer!
}


void ShapeMonitor::watchedObjectRenamedCallback( MObject & node, void* clientData )
{
	MonitoredObject *mon = (MonitoredObject*) clientData;

	privateInstance->stopWatching(mon->mayaNodeName);
	// Note: after this call the monitored object has been deleted, 
	// so don't do anything with this pointer!
}

